//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjExample01.h"
#include "NjSemaphoreTest.h"
#include "NjSynchronizationTest.h"
#include "NjSharedSyncObjectTest.h"
#include "NjDebugSystemTestImplementation.h"
#include "NjSharedSyncObjectConstants.h"
#include "NjThreadsSystem.h"
#include "NjMemUtils.h"
#include "NjRandUint.h"
#include "NjExampleRandGenerator.h"
#include "NjTestUnit.h"

//----------------------------------------------------------------------------
class NjSemaphoreTest::Static
{
public:
	static void RunSemaphoreFunctionalMultithreaded();
	static void RunSemaphoreFunctionalThread(void * pData);
	static void RunSemaphoreAssert();
	static void RunSemaphoreAssertThread(void * pData);

	enum ErrorState
	{
		STATUS_SUCCESS,
		NOT_INITIALIZED
	};

	class SemaphoreFunctionalSharedData
	{
	public:
		NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(SemaphoreFunctionalSharedData);
		NJ_TRIVIAL_DESTRUCTOR(SemaphoreFunctionalSharedData);

		static const NjUint TOT_THREADS = 20;
		static const NjUint SEMAPHORE_INITIAL_COUNT = 5;
		static const NjUint TOT_SEMAPHORES = 5;
		static const NjUint ITERATIONS = 1000;
		static const NjUint TOT_ENTRIES = ITERATIONS*TOT_THREADS*2;

		class ThreadData
		{
		public:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(ThreadData);
			NJ_TRIVIAL_DESTRUCTOR(ThreadData);

			SemaphoreFunctionalSharedData * m_pSharedData;
			NjThreadsSystem::Thread * m_pThread;
			ErrorState m_eErrorState;

			NJ_DISABLE_COPY(ThreadData);
		};

		class SemaphoreData
		{
		public:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(SemaphoreData);
			NJ_TRIVIAL_DESTRUCTOR(SemaphoreData);

			NjSemaphore * m_pSemaphore;
			NjAtomic<NjUint> m_kLockCount;

			NJ_DISABLE_COPY(SemaphoreData);
		};

		SemaphoreData m_aSemaphoreData[TOT_SEMAPHORES];
		ThreadData m_aThreadsData[TOT_THREADS];

		enum LockState
		{
			TRY_LOCK_START,
			TRY_LOCK_END,
			TRY_LOCK_FAIL,
			UNLOCK_START,
			UNLOCK_END
		};

		class Entry
		{
		public:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(Entry);
			NJ_TRIVIAL_DESTRUCTOR(Entry);

			NjUint m_uSemaphore;
			LockState m_eLockState;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			NjUint8 m_aSnapshot[sizeof(NjSemaphore)];
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			ThreadData * m_pThreadData;

			NJ_DISABLE_COPY(Entry);
		};

		Entry m_aEntries[TOT_ENTRIES];
		NjAtomic<NjUint> m_kTotEntries;

		NJ_DISABLE_COPY(SemaphoreFunctionalSharedData);
	};

	NJ_DISABLE_INSTANCE(Static);
};
//----------------------------------------------------------------------------
void NjSemaphoreTest::Run(void)
{
	NjSharedSyncObjectTest<NjSemaphore>::RunFunctional();
	NjSharedSyncObjectTest<NjSemaphore>::RunFunctionalMultithreaded();
	Static::RunSemaphoreFunctionalMultithreaded();
	NjSharedSyncObjectTest<NjSemaphore>::RunPerformances();
	Static::RunSemaphoreAssert();
	NjSharedSyncObjectTest<NjSemaphore>::RunLocker();
}
//----------------------------------------------------------------------------
void NjSemaphoreTest::Static::RunSemaphoreFunctionalMultithreaded()
{
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjSemaphore>::ms_szSyncObject);
		NJ_TEST_UNIT("Constructors, Destructors, TryLock, Unlock (multi threaded)");
			SemaphoreFunctionalSharedData * const pSharedData(NjNew(SemaphoreFunctionalSharedData));

			SemaphoreFunctionalSharedData::SemaphoreData * pSemaphore=pSharedData->m_aSemaphoreData;
			SemaphoreFunctionalSharedData::SemaphoreData const * const pSemaphoreEnd=pSemaphore+SemaphoreFunctionalSharedData::TOT_SEMAPHORES;
			while(pSemaphore<pSemaphoreEnd)
			{
				pSemaphore->m_pSemaphore=NjNew(NjSemaphore(SemaphoreFunctionalSharedData::SEMAPHORE_INITIAL_COUNT, SemaphoreFunctionalSharedData::SEMAPHORE_INITIAL_COUNT));
				pSemaphore->m_kLockCount.Set(0);
				++pSemaphore;
			}

			pSharedData->m_kTotEntries.Set(0);

			SemaphoreFunctionalSharedData::ThreadData * pPos(pSharedData->m_aThreadsData);
			SemaphoreFunctionalSharedData::ThreadData * const pEnd(pPos+SemaphoreFunctionalSharedData::TOT_THREADS);

			while (pPos!=pEnd)
			{
				SemaphoreFunctionalSharedData::ThreadData & kThreadData(*pPos);
				++pPos;

				kThreadData.m_pSharedData=pSharedData;
				kThreadData.m_pThread=NjThreadsSystem::CreateThread(RunSemaphoreFunctionalThread, &kThreadData);

				kThreadData.m_eErrorState=NOT_INITIALIZED;

				NJ_TEST(kThreadData.m_pThread!=NJ_NULL);
			}

			pPos=pSharedData->m_aThreadsData;

			while (pPos!=pEnd)
			{
				SemaphoreFunctionalSharedData::ThreadData & kThreadData(*pPos);
				++pPos;

				NJ_TEST(NjThreadsSystem::TryJoinThread(kThreadData.m_pThread, (pEnd+1-pPos)*SemaphoreFunctionalSharedData::ITERATIONS*NjSharedSyncObjectConstants::TIME_GRANULARITY)==NjEventsSystem::TRY_SUCCESS);
				NJ_TEST(NjThreadsSystem::DestroyThread(kThreadData.m_pThread));
				NJ_TEST(kThreadData.m_eErrorState==STATUS_SUCCESS);
			}

			SemaphoreFunctionalSharedData::Entry const * pEntry(pSharedData->m_aEntries);
			SemaphoreFunctionalSharedData::Entry const * const pEntryEnd(pEntry+pSharedData->m_kTotEntries.Get());

			NjSemaphore * aOwnedSemaphore[SemaphoreFunctionalSharedData::TOT_THREADS];
			NjMemUtils::Zero(aOwnedSemaphore, sizeof(aOwnedSemaphore));

			NjUint aSemaphoreCounter[SemaphoreFunctionalSharedData::TOT_SEMAPHORES];
			NjMemUtils::Zero(aSemaphoreCounter, sizeof(aSemaphoreCounter));

			while (pEntry<pEntryEnd)
			{
				NjUint const uThread(pEntry->m_pThreadData-pSharedData->m_aThreadsData);
				NJ_TEST(uThread<SemaphoreFunctionalSharedData::TOT_THREADS);

				NjUint const uSemaphore(pEntry->m_uSemaphore);
				NJ_TEST(uSemaphore<SemaphoreFunctionalSharedData::TOT_SEMAPHORES);

				NjSemaphore * const pSemaphore(pSharedData->m_aSemaphoreData[uSemaphore].m_pSemaphore);

				switch(pEntry->m_eLockState)
				{
					case SemaphoreFunctionalSharedData::TRY_LOCK_START:
						NJ_TEST(aOwnedSemaphore[uThread]==NJ_NULL);
						aOwnedSemaphore[uThread]=pSemaphore;
						break;

					case SemaphoreFunctionalSharedData::TRY_LOCK_END:
						NJ_TEST(aOwnedSemaphore[uThread]==pSemaphore);
						++(aSemaphoreCounter[uSemaphore]);
						aOwnedSemaphore[uThread]=NJ_NULL;
						NJ_TEST(aSemaphoreCounter[uSemaphore]<=SemaphoreFunctionalSharedData::SEMAPHORE_INITIAL_COUNT);
						break;

					case SemaphoreFunctionalSharedData::TRY_LOCK_FAIL:
						NJ_TEST(aOwnedSemaphore[uThread]==pSemaphore);
						aOwnedSemaphore[uThread]=NJ_NULL;
						break;

					case SemaphoreFunctionalSharedData::UNLOCK_START:
						NJ_TEST(aOwnedSemaphore[uThread]==NJ_NULL);
						aOwnedSemaphore[uThread]=pSemaphore;
						NJ_TEST(aSemaphoreCounter[uSemaphore]>0);
						--(aSemaphoreCounter[uSemaphore]);
						break;

					case SemaphoreFunctionalSharedData::UNLOCK_END:
						NJ_TEST(aOwnedSemaphore[uThread]==pSemaphore);
						aOwnedSemaphore[uThread]=NJ_NULL;
						break;

					default:
						NJ_TEST_FAIL();
				}
				++pEntry;
			}

			pSemaphore=pSharedData->m_aSemaphoreData;
			while(pSemaphore<pSemaphoreEnd)
			{
				NjDelete(pSemaphore->m_pSemaphore);
				++pSemaphore;
			}

			NjDelete(pSharedData);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
}
//----------------------------------------------------------------------------
void NjSemaphoreTest::Static::RunSemaphoreFunctionalThread(void * pData)
{
	SemaphoreFunctionalSharedData::ThreadData & kThreadData(*reinterpret_cast<SemaphoreFunctionalSharedData::ThreadData *>(pData));
	SemaphoreFunctionalSharedData & kSharedData(*kThreadData.m_pSharedData);
	NjRandUint<EXAMPLE_RAND_GENERATOR> kRand(0, 1000000);

	NjUint uIterations(SemaphoreFunctionalSharedData::ITERATIONS);
	while (uIterations>0)
	{
		--uIterations;

		NjUint uSemaphore(kRand()%SemaphoreFunctionalSharedData::TOT_SEMAPHORES);
		SemaphoreFunctionalSharedData::SemaphoreData & kSemaphoreData(kSharedData.m_aSemaphoreData[uSemaphore]);

		NjUint uAction(kRand()%2); // 0 UNLOCK, 1 LOCK

		if (uAction==0)
		{
			NjUint uLockCount=kSemaphoreData.m_kLockCount.Get();

			for(;;)
			{
				if (uLockCount==0)
				{
					uAction=1;
					break;
				}

				NjUint const uNewLockCount(uLockCount-1);
				NjUint const uOldLockCount(kSemaphoreData.m_kLockCount.PostCompareExchange(uLockCount, uNewLockCount));

				if (uOldLockCount==uLockCount)
					break;

				uLockCount=uOldLockCount;
			}
		}

		if (uAction==0)
		{
			NjUint uIndex(kSharedData.m_kTotEntries.PostIncrement());
			SemaphoreFunctionalSharedData::Entry * pEntry(kSharedData.m_aEntries+uIndex);
			pEntry->m_uSemaphore=uSemaphore;
			pEntry->m_pThreadData=&kThreadData;
			pEntry->m_eLockState=SemaphoreFunctionalSharedData::UNLOCK_START;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), kSemaphoreData.m_pSemaphore, sizeof(NjSemaphore));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

			kSemaphoreData.m_pSemaphore->Unlock();

			uIndex=kSharedData.m_kTotEntries.PostIncrement();
			pEntry=kSharedData.m_aEntries+uIndex;
			pEntry->m_uSemaphore=uSemaphore;
			pEntry->m_pThreadData=&kThreadData;
			pEntry->m_eLockState=SemaphoreFunctionalSharedData::UNLOCK_END;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), kSemaphoreData.m_pSemaphore, sizeof(NjSemaphore));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			continue;
		}

		NjUint uIndex(kSharedData.m_kTotEntries.PostIncrement());
		SemaphoreFunctionalSharedData::Entry * pEntry(kSharedData.m_aEntries+uIndex);
		pEntry->m_uSemaphore=uSemaphore;
		pEntry->m_pThreadData=&kThreadData;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), kSemaphoreData.m_pSemaphore, sizeof(NjSemaphore));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

		pEntry->m_eLockState=SemaphoreFunctionalSharedData::TRY_LOCK_START;
		if (!kSemaphoreData.m_pSemaphore->TryLock(NjSharedSyncObjectConstants::TIME_GRANULARITY))
		{
			uIndex=kSharedData.m_kTotEntries.PostIncrement();
			pEntry=kSharedData.m_aEntries+uIndex;
			pEntry->m_uSemaphore=uSemaphore;
			pEntry->m_pThreadData=&kThreadData;
			pEntry->m_eLockState=SemaphoreFunctionalSharedData::TRY_LOCK_FAIL;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), kSemaphoreData.m_pSemaphore, sizeof(NjSemaphore));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			continue;
		}

		uIndex=kSharedData.m_kTotEntries.PostIncrement();
		pEntry=kSharedData.m_aEntries+uIndex;
		pEntry->m_uSemaphore=uSemaphore;
		pEntry->m_pThreadData=&kThreadData;
		pEntry->m_eLockState=SemaphoreFunctionalSharedData::TRY_LOCK_END;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), kSemaphoreData.m_pSemaphore, sizeof(NjSemaphore));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

		kSemaphoreData.m_kLockCount.Increment();
	}

	kThreadData.m_eErrorState=STATUS_SUCCESS;
}
//----------------------------------------------------------------------------
void NjSemaphoreTest::Static::RunSemaphoreAssert()
{
#if NJ_SEMAPHORE_STORE_USERS_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjSemaphore>::ms_szSyncObject);
		NJ_TEST_UNIT("Assertion (destruction)");
			NjSmartPtr<NjDebugSystem::Implementation> spImplementation(NjDebugSystem::GetImplementation());
			NjDebugSystemTestImplementation * const pTestBreakpoint(NjNew(NjDebugSystemTestImplementation(spImplementation)));
			NJ_TEST(pTestBreakpoint!=NJ_NULL);
			NjDebugSystem::SetImplementation(pTestBreakpoint);

			NjThreadsSystem::Thread * pThread;

			{
				NjSemaphore kSyncObject(0, 1);
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				pThread=NjThreadsSystem::CreateThread(RunSemaphoreAssertThread, &kSyncObject);

				NJ_TEST(pThread!=NJ_NULL);
				NJ_TEST(NjThreadsSystem::Sleep(NjSharedSyncObjectConstants::TIME_GRANULARITY));
			}

			NJ_TEST(pTestBreakpoint->WasTriggeredBreakpoint());

			NJ_TEST(NjThreadsSystem::TryJoinThread(pThread, NjSharedSyncObjectConstants::TIME_GRANULARITY*5)==NjEventsSystem::TRY_SUCCESS);
			NJ_TEST(NjThreadsSystem::DestroyThread(pThread));

			NjDebugSystem::SetImplementation(spImplementation);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
#endif // NJ_SEMAPHORE_STORE_USERS_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED

#if NJ_SEMAPHORE_ENFORCE_MAX_COUNTER && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjSemaphore>::ms_szSyncObject);
		NJ_TEST_UNIT("Assertion (max counter)");
			NjSmartPtr<NjDebugSystem::Implementation> spImplementation(NjDebugSystem::GetImplementation());
			NjDebugSystemTestImplementation * const pTestBreakpoint(NjNew(NjDebugSystemTestImplementation(spImplementation)));
			NJ_TEST(pTestBreakpoint!=NJ_NULL);
			NjDebugSystem::SetImplementation(pTestBreakpoint);

			{
				NjSemaphore kSyncObject(0, 3);
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				kSyncObject.Unlock();
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				kSyncObject.Unlock(3);
				NJ_TEST(pTestBreakpoint->WasTriggeredBreakpoint());
			}

			{
				NjSemaphore kSyncObject(0, 3);
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				kSyncObject.Unlock();
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				kSyncObject.Unlock(2);
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());
			}

			NjDebugSystem::SetImplementation(spImplementation);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
#endif // NJ_SEMAPHORE_ENFORCE_MAX_COUNTER && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
}
//----------------------------------------------------------------------------
void NjSemaphoreTest::Static::RunSemaphoreAssertThread(void * pData)
{
	NjSemaphore * const pSyncObject(reinterpret_cast<NjSemaphore *>(pData));
	pSyncObject->TryLock(NjSharedSyncObjectConstants::TIME_GRANULARITY*3);
}
//----------------------------------------------------------------------------
